package tcp

import (
	"context"
	"fmt"
	"gitee.com/easy_code/go-redis/interface/tcp"
	"gitee.com/easy_code/go-redis/lib/logger"
	"net"
	"os"
	"os/signal"
	"sync"
	"syscall"
)

// Config TCP 相关配置
type Config struct {
	Address string // 监听地址。格式，ip:端口
}

// ListenAndServeWithSignal 处理系统相关信号
func ListenAndServeWithSignal(cfg *Config, handler tcp.Handler) error {

	// closeChan 用于监听关闭 listen 的信号，然后传递给 ListenAndServe 对相关连接进行处理
	// 如：用户强制 kill 掉进程
	closeChan := make(chan struct{})

	// signal.Notify 注册监听的信号。当系统监听到指定信号时，会将信号传给 signChan
	sigChan := make(chan os.Signal)
	// 程序被杀掉、挂起相关信号：syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT
	signal.Notify(sigChan, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
	go func() {
		// 监听到系统信号时，给 closeChan 发送数据
		sig := <-sigChan
		switch sig {
		case syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
			closeChan <- struct{}{}
		}
	}()

	listener, err := net.Listen("tcp", cfg.Address)
	if err != nil {
		return err
	}
	logger.Info(fmt.Sprintf("bind: %s, start listening...", cfg.Address))
	ListenAndServe(listener, handler, closeChan)
	return nil
}

// ListenAndServe binds port and handle requests, blocking until close
func ListenAndServe(listener net.Listener, handler tcp.Handler, closeChan <-chan struct{}) {
	// 监听关闭信号
	go func() {
		<-closeChan
		// 一旦 channel 读取到关闭信号，执行下面逻辑
		logger.Info("shutting down")
		_ = listener.Close() // listener.Accept() will return err immediately
		_ = handler.Close()  // close connections
	}()

	// listen port
	defer func() {
		// 退出时关闭连接
		_ = listener.Close()
		_ = handler.Close()
	}()
	ctx := context.Background()
	// waitDone 用于等待全部已连接的客户端退出，保证全部客户端退出才终止程序
	var waitDone sync.WaitGroup
	for true {
		// 接收新连接
		conn, err := listener.Accept()
		if err != nil {
			// 接收新连接出现错误，跳出当前循环
			break
		}
		logger.Info("accepted link")

		// 一个协程处理一个连接
		waitDone.Add(1)
		go func() {
			defer waitDone.Done() // 防止 Handle 出现 panic，无法执行 Done
			handler.Handle(ctx, conn)
		}()
	}
	// 等待全部已连接客户端都处理完，在退出
	waitDone.Wait()
}
